// This code is derived from jcifs smb client library <jcifs at samba dot org>
// Ported by J. Arturo <webmaster at komodosoft dot net>
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
using System.IO;
using SharpCifs.Util;
using SharpCifs.Util.Sharpen;

namespace SharpCifs.Smb
{
	public class Dfs
	{
		internal class CacheEntry
		{
			internal long Expiration;

			internal Hashtable Map;

			internal CacheEntry(long ttl)
			{
				if (ttl == 0)
				{
					ttl = Ttl;
				}
				Expiration = Runtime.CurrentTimeMillis() + ttl * 1000L;
				Map = new Hashtable();
			}
		}

		internal static LogStream Log = LogStream.GetInstance();

		internal static readonly bool StrictView = Config.GetBoolean("jcifs.smb.client.dfs.strictView"
			, false);

		internal static readonly long Ttl = Config.GetLong("jcifs.smb.client.dfs.ttl", 300
			);

		internal static readonly bool Disabled = Config.GetBoolean("jcifs.smb.client.dfs.disabled"
			, false);

		internal static CacheEntry FalseEntry = new CacheEntry(0L);

		internal CacheEntry Domains;

		internal CacheEntry Referrals;

		/// <exception cref="SharpCifs.Smb.SmbAuthException"></exception>
		public virtual Hashtable GetTrustedDomains(NtlmPasswordAuthentication auth)
		{
			if (Disabled || auth.Domain == "?")
			{
				return null;
			}
			if (Domains != null && Runtime.CurrentTimeMillis() > Domains.Expiration)
			{
				Domains = null;
			}
			if (Domains != null)
			{
				return Domains.Map;
			}
			try
			{
				UniAddress addr = UniAddress.GetByName(auth.Domain, true);
				SmbTransport trans = SmbTransport.GetSmbTransport(addr, 0);
				CacheEntry entry = new CacheEntry(Ttl * 10L);
				DfsReferral dr = trans.GetDfsReferrals(auth, string.Empty, 0);
				if (dr != null)
				{
					DfsReferral start = dr;
					do
					{
						string domain = dr.Server.ToLower();
						entry.Map.Put(domain, new Hashtable());
						dr = dr.Next;
					}
					while (dr != start);
					Domains = entry;
					return Domains.Map;
				}
			}
			catch (IOException ioe)
			{
				if (Log.Level >= 3)
				{
					Runtime.PrintStackTrace(ioe, Log);
				}
				if (StrictView && ioe is SmbAuthException)
				{
					throw (SmbAuthException)ioe;
				}
			}
			return null;
		}

		/// <exception cref="SharpCifs.Smb.SmbAuthException"></exception>
		public virtual bool IsTrustedDomain(string domain, NtlmPasswordAuthentication auth
			)
		{
			Hashtable domains = GetTrustedDomains(auth);
			if (domains == null)
			{
				return false;
			}
			domain = domain.ToLower();
			return domains.Get(domain) != null;
		}

		/// <exception cref="SharpCifs.Smb.SmbAuthException"></exception>
		public virtual SmbTransport GetDc(string domain, NtlmPasswordAuthentication auth)
		{
			if (Disabled)
			{
				return null;
			}
			try
			{
				UniAddress addr = UniAddress.GetByName(domain, true);
				SmbTransport trans = SmbTransport.GetSmbTransport(addr, 0);
				DfsReferral dr = trans.GetDfsReferrals(auth, "\\" + domain, 1);
				if (dr != null)
				{
					DfsReferral start = dr;
					IOException e = null;
					do
					{
						try
						{
							addr = UniAddress.GetByName(dr.Server);
							return SmbTransport.GetSmbTransport(addr, 0);
						}
						catch (IOException ioe)
						{
							e = ioe;
						}
						dr = dr.Next;
					}
					while (dr != start);
					throw e;
				}
			}
			catch (IOException ioe)
			{
				if (Log.Level >= 3)
				{
					Runtime.PrintStackTrace(ioe, Log);
				}
				if (StrictView && ioe is SmbAuthException)
				{
					throw (SmbAuthException)ioe;
				}
			}
			return null;
		}

		/// <exception cref="SharpCifs.Smb.SmbAuthException"></exception>
		public virtual DfsReferral GetReferral(SmbTransport trans, string domain, string 
			root, string path, NtlmPasswordAuthentication auth)
		{
			if (Disabled)
			{
				return null;
			}
			try
			{
				string p = "\\" + domain + "\\" + root;
				if (path != null)
				{
					p += path;
				}
				DfsReferral dr = trans.GetDfsReferrals(auth, p, 0);
				if (dr != null)
				{
					return dr;
				}
			}
			catch (IOException ioe)
			{
				if (Log.Level >= 4)
				{
					Runtime.PrintStackTrace(ioe, Log);
				}
				if (StrictView && ioe is SmbAuthException)
				{
					throw (SmbAuthException)ioe;
				}
			}
			return null;
		}

		/// <exception cref="SharpCifs.Smb.SmbAuthException"></exception>
		public virtual DfsReferral Resolve(string domain, string root, string path, NtlmPasswordAuthentication
			 auth)
		{
			lock (this)
			{
				DfsReferral dr = null;
				long now = Runtime.CurrentTimeMillis();
				if (Disabled || root.Equals("IPC$"))
				{
					return null;
				}
				Hashtable domains = GetTrustedDomains(auth);
				if (domains != null)
				{
					domain = domain.ToLower();
					Hashtable roots = (Hashtable)domains.Get(domain);
					if (roots != null)
					{
						SmbTransport trans = null;
						root = root.ToLower();
						CacheEntry links = (CacheEntry)roots.Get(root);
						if (links != null && now > links.Expiration)
						{
							//Sharpen.Collections.Remove(roots, root);
                            roots.Remove(root);
							links = null;
						}
						if (links == null)
						{
							if ((trans = GetDc(domain, auth)) == null)
							{
								return null;
							}
							dr = GetReferral(trans, domain, root, path, auth);
							if (dr != null)
							{
								int len = 1 + domain.Length + 1 + root.Length;
								links = new CacheEntry(0L);
								DfsReferral tmp = dr;
								do
								{
									if (path == null)
									{
										// TODO: fix this
                                        //tmp.map = links.map;
										tmp.Key = "\\";
									}
									tmp.PathConsumed -= len;
									tmp = tmp.Next;
								}
								while (tmp != dr);
								if (dr.Key != null)
								{
									links.Map.Put(dr.Key, dr);
								}
								roots.Put(root, links);
							}
							else
							{
								if (path == null)
								{
									roots.Put(root, FalseEntry);
								}
							}
						}
						else
						{
							if (links == FalseEntry)
							{
								links = null;
							}
						}
						if (links != null)
						{
							string link = "\\";
							dr = (DfsReferral)links.Map.Get(link);
							if (dr != null && now > dr.Expiration)
							{
								//Sharpen.Collections.Remove(links.map, link);
                                links.Map.Remove(link);
								dr = null;
							}
							if (dr == null)
							{
								if (trans == null)
								{
									if ((trans = GetDc(domain, auth)) == null)
									{
										return null;
									}
								}
								dr = GetReferral(trans, domain, root, path, auth);
								if (dr != null)
								{
									dr.PathConsumed -= 1 + domain.Length + 1 + root.Length;
									dr.Link = link;
									links.Map.Put(link, dr);
								}
							}
						}
					}
				}
				if (dr == null && path != null)
				{
					if (Referrals != null && now > Referrals.Expiration)
					{
						Referrals = null;
					}
					if (Referrals == null)
					{
						Referrals = new CacheEntry(0);
					}
					string key = "\\" + domain + "\\" + root;
					if (path.Equals("\\") == false)
					{
						key += path;
					}
					key = key.ToLower();
				    //ListIterator<object> iter = new ListIterator<object>(referrals.map.Keys.GetEnumerator(), 0);
                    foreach (var current in Referrals.Map.Keys)
                    {
                        string _key = (string)current;
						int klen = _key.Length;
						bool match = false;
						if (klen == key.Length)
						{
							match = _key.Equals(key);
						}
						else
						{
							if (klen < key.Length)
							{
								match = _key.RegionMatches(false, 0, key, 0, klen) && key[klen] == '\\';
							}
						}
						if (match)
						{
							dr = (DfsReferral)Referrals.Map.Get(_key);
						}
					}
				}
				return dr;
			}
		}

		internal virtual void Insert(string path, DfsReferral dr)
		{
			lock (this)
			{
				int s1;
				int s2;
				string server;
				string share;
				string key;
				if (Disabled)
				{
					return;
				}
				s1 = path.IndexOf('\\', 1);
				s2 = path.IndexOf('\\', s1 + 1);
				server = Runtime.Substring(path, 1, s1);
				share = Runtime.Substring(path, s1 + 1, s2);
				key = Runtime.Substring(path, 0, dr.PathConsumed).ToLower();
				int ki = key.Length;
				while (ki > 1 && key[ki - 1] == '\\')
				{
					ki--;
				}
				if (ki < key.Length)
				{
					key = Runtime.Substring(key, 0, ki);
				}
				dr.PathConsumed -= 1 + server.Length + 1 + share.Length;
				if (Referrals != null && (Runtime.CurrentTimeMillis() + 10000) > Referrals.Expiration)
				{
					Referrals = null;
				}
				if (Referrals == null)
				{
					Referrals = new CacheEntry(0);
				}
				Referrals.Map.Put(key, dr);
			}
		}
	}
}
